use core::fmt;
use regex::Regex;
use std::{
    fs,
    io::{Read, Write},
    path::{Path, PathBuf},
};

const DOCGEN_RE: &str = r"## @.*(\n##.*)*(\n.*)";

#[derive(Clone, Copy, Debug, PartialEq)]
enum GDHeaderKind {
    Singleton,
    Script,
    Class,
}

#[derive(Clone, Copy, Debug, PartialEq)]
enum GDFuncKind {
    Method,
    Override,
    Virtual,
}

#[derive(Clone, Copy, Debug, PartialEq)]
enum GDElemKind {
    Property,
    Signal,
    Function(GDFuncKind),
}

#[derive(Clone, Copy, Debug, PartialEq)]
enum GDAnnoKind {
    Header(GDHeaderKind),
    Element(GDElemKind),
}

#[derive(Clone, Copy, Debug, PartialEq)]
enum GDScriptKind {
    Class,
    Script,
    Singleton,
}

#[derive(Debug)]
pub enum DocgenErr {
    IoError(std::io::Error),
    ParsingError(ParsingError),
}

#[derive(Debug)]
pub struct ParsingError {
    line: String,
    info: String,
}

impl std::error::Error for DocgenErr {
    fn cause(&self) -> Option<&dyn std::error::Error> {
        self.source()
    }

    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            DocgenErr::IoError(err) => Some(err),
            DocgenErr::ParsingError(err) => Some(err),
        }
    }

    fn description(&self) -> &str {
        "description() is deprecated; use Display"
    }
}

impl fmt::Display for DocgenErr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            DocgenErr::IoError(err) => write!(f, "IO Error:: {}", err),
            DocgenErr::ParsingError(err) => write!(f, "Parsing Error:: {}", err),
        }
    }
}

impl From<std::io::Error> for DocgenErr {
    fn from(err: std::io::Error) -> Self {
        DocgenErr::IoError(err)
    }
}

impl From<ParsingError> for DocgenErr {
    fn from(err: ParsingError) -> Self {
        DocgenErr::ParsingError(err)
    }
}

impl From<regex::Error> for ParsingError {
    fn from(err: regex::Error) -> Self {
        ParsingError::new(&err.to_string(), "")
    }
}

impl std::error::Error for ParsingError {}

impl fmt::Display for ParsingError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}, at line '{}'.", self.info, self.line)
    }
}

impl ParsingError {
    pub fn new(info: &str, line: &str) -> ParsingError {
        ParsingError {
            info: info.to_string(),
            line: line.to_string(),
        }
    }
}

#[derive(Debug, Clone)]
pub struct GDAnno {
    content: String,
    kind: GDAnnoKind,
}

pub struct GDElement {
    content: String,
    kind: GDElemKind,
    anno: GDAnno,
}

pub struct GDScript {
    name: String,
    header: GDAnno,
    elems: Vec<GDElement>,
    kind: GDScriptKind,
    extends: String,
}

impl ToString for GDAnno {
    fn to_string(&self) -> String {
        let mut result = String::new();
        result.push_str(&format!("\n{}\n", self.content));
        result
    }
}

impl TryFrom<&str> for GDAnno {
    type Error = ParsingError;
    fn try_from(value: &str) -> Result<Self, Self::Error> {
        let mut kind: GDAnnoKind = GDAnnoKind::Header(GDHeaderKind::Script);
        let mut content = String::new();
        let mut is_content_start = false;
        for line in value.lines().filter(|x| x.starts_with("## ")) {
            // 去除注释开头的 `## `
            let line = line.replace("## ", "");
            match &line {
                n if n.starts_with("@class")
                    || n.starts_with("@script")
                    || n.starts_with("@singleton") =>
                {
                    if is_content_start {
                        return Err(ParsingError::new(
                            "Cannot declare annotation kind twice",
                            &line,
                        ));
                    }
                    if n.starts_with("@class") {
                        kind = GDAnnoKind::Header(GDHeaderKind::Class);
                    } else if n.starts_with("@script") {
                        kind = GDAnnoKind::Header(GDHeaderKind::Script);
                    } else if n.starts_with("@singleton") {
                        kind = GDAnnoKind::Header(GDHeaderKind::Singleton);
                    }
                    is_content_start = true;
                }
                n if n.starts_with("@property")
                    || n.starts_with("@method")
                    || n.starts_with("@signal")
                    || n.starts_with("@virtual")
                    || n.starts_with("@override") =>
                {
                    if is_content_start {
                        return Err(ParsingError::new(
                            "Cannot declare annotation kind twice",
                            &line,
                        ));
                    } else if n.starts_with("@property") {
                        kind = GDAnnoKind::Element(GDElemKind::Property);
                    } else if n.starts_with("@method") {
                        kind = GDAnnoKind::Element(GDElemKind::Function(GDFuncKind::Method));
                    } else if n.starts_with("@signal") {
                        kind = GDAnnoKind::Element(GDElemKind::Signal);
                    } else if n.starts_with("@virtual") {
                        kind = GDAnnoKind::Element(GDElemKind::Function(GDFuncKind::Virtual));
                    } else if n.starts_with("@override") {
                        kind = GDAnnoKind::Element(GDElemKind::Function(GDFuncKind::Override));
                    }
                    is_content_start = true;
                }
                _ => {
                    // 如果已经读取到注释类型，那么接下来读取注释内容；否则将会报错。
                    if is_content_start {
                        content.push_str(&line);
                    } else if line.starts_with("@") {
                        return Err(ParsingError::new(
                            "Unrecognized annotation kind declaration",
                            &line,
                        ));
                    } else {
                        return Err(ParsingError::new("No annotation kind declarated", &line));
                    }
                }
            }
        }
        Ok(GDAnno { kind, content })
    }
}

impl ToString for GDElement {
    fn to_string(&self) -> String {
        let mut result = String::new();
        result.push_str(&format!("#### {}\n", self.content));
        result.push_str(&format!("{}\n\n", self.anno.content));
        result
    }
}

impl TryFrom<&str> for GDElement {
    type Error = ParsingError;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        /* 每个GDElement 都由注释开始，由单行内容结束。
        ```
        ## @property
        ## xxxxxx
        var x = 1

        ```
        */
        let anno = GDAnno::try_from(
            value
                .lines()
                .filter(|x| x.starts_with("## "))
                .fold(String::from(""), |sum, x| sum.to_string() + x + "\n")
                .as_str(),
        )?;
        let content = value
            .lines()
            .filter(|x| !x.starts_with("##"))
            .fold(String::from(""), |sum, x| sum.to_string() + x + "\n");
        let kind: GDElemKind = {
            match &content {
                n if n.starts_with("export")
                    || n.starts_with("onready")
                    || n.starts_with("var")
                    || n.starts_with("const")
                    || n.starts_with("enum") =>
                {
                    GDElemKind::Property
                }
                n if n.starts_with("func") => {
                    if let GDAnnoKind::Element(GDElemKind::Function(x)) = &anno.kind {
                        GDElemKind::Function(*x)
                    } else {
                        return Err(ParsingError::new(&format!("The specific type of the element does not match that declared in the comment: Got {:?}, but expected GDElemKind::Function(x)", &anno.kind), &content));
                    }
                }
                n if n.starts_with("signal") => GDElemKind::Signal,
                _ => return Err(ParsingError::new("Unknown content", &content)),
            }
        };
        if matches!(&anno.kind, &GDAnnoKind::Header(_)) {
            return Err(ParsingError::new(
                "Cannot declare a element as Header",
                value,
            ));
        }
        if GDAnnoKind::Element(kind) != anno.kind {
            return Err(ParsingError::new(&format!("The specific type of the element does not match that declared in the comment: Got {:?}, but expected {:?}", &anno.kind, &kind), &content));
        }
        Ok(GDElement {
            content,
            kind,
            anno,
        })
    }
}

impl GDScript {
    pub fn read_from_file(path: &Path) -> Result<GDScript, DocgenErr> {
        let file = {
            let mut f = fs::File::open(path)?;
            let mut buff = String::new();
            f.read_to_string(&mut buff)?;
            buff
        };
        let mut s = GDScript::try_from(file.as_str())?;
        if !matches!(s.kind, GDScriptKind::Class) {
            s.name = path.file_name().unwrap().to_str().unwrap().to_string();
        }
        Ok(s)
    }
    pub fn write_to_file(&self, folder_path: &str) -> Result<(), DocgenErr> {
        let mut file = {
            let mut p = PathBuf::from(folder_path);
            p.push(format!("{}.md", self.name));
            fs::File::create(p)?
        };
        let content = {
            let mut f = String::new();
            /* 添加文件头 */
            f.push_str(&format!(
                "# {}:: {}\n",
                self.name,
                match self.kind {
                    GDScriptKind::Class => "Class",
                    GDScriptKind::Script => "Script",
                    GDScriptKind::Singleton => "Singleton",
                }
            ));
            /* 添加继承条目 */
            f.push_str(&format!("*extends {}*\n\n", self.extends));
            f.push_str(&format!("{}\n\n", self.header.content));
            /* 属性 */
            f.push_str("## Properties\n\n");
            for i in self
                .elems
                .iter()
                .filter(|&x| matches!(x.kind, GDElemKind::Property))
                .collect::<Vec<_>>()
            {
                f.push_str(&i.to_string());
            }
            f.push_str("## Funtions\n\n");
            f.push_str("### Overrides\n\n");
            for i in self
                .elems
                .iter()
                .filter(|&x| matches!(x.kind, GDElemKind::Function(GDFuncKind::Override)))
                .collect::<Vec<_>>()
            {
                f.push_str(&i.to_string());
            }
            f.push_str("### Methods\n\n");
            for i in self
                .elems
                .iter()
                .filter(|&x| matches!(x.kind, GDElemKind::Function(GDFuncKind::Method)))
                .collect::<Vec<_>>()
            {
                f.push_str(&i.to_string());
            }
            f.push_str("### Virtuals\n\n");
            for i in self
                .elems
                .iter()
                .filter(|&x| matches!(x.kind, GDElemKind::Function(GDFuncKind::Virtual)))
                .collect::<Vec<_>>()
            {
                f.push_str(&i.to_string());
            }
            f
        };
        file.write_all(content.as_bytes())?;
        Ok(())
    }
}

impl TryFrom<&str> for GDScript {
    type Error = ParsingError;
    fn try_from(value: &str) -> Result<Self, Self::Error> {
        let mut elems: Vec<GDElement> = vec![];
        let mut extends: String = String::from("Object");
        let mut class_name: Option<String> = None;

        let docs: Vec<String> = Regex::new(DOCGEN_RE)?
            .find_iter(value)
            .map(|x| x.as_str().to_string())
            .collect();
        let (class_doc, elem_docs) = docs.split_at(1);
        let class_doc: String = class_doc[0]
            .lines()
            .filter(|&x| x.starts_with("##"))
            .map(|x| format!("{}\n", x))
            .collect();
        let header_anno = GDAnno::try_from(class_doc.as_str())?;
        for i in elem_docs {
            elems.push(GDElement::try_from(i.as_str())?)
        }
        for line in value.lines() {
            // 包含 class_name
            if line.starts_with("class_name ") {
                class_name = Some(String::from(line.split_whitespace().collect::<Vec<_>>()[1]));
            }
            // 包含 extends 并且不是内部类
            if line.contains("extends ") && !line.contains("class ") {
                extends = String::from(line.split("extends").collect::<Vec<_>>()[1])
            }
        }
        /* name 字段赋值 */
        let (name, kind) = {
            match &header_anno.clone().kind {
                GDAnnoKind::Header(GDHeaderKind::Class) => {
                    if let Some(x) = &class_name {
                        (x.clone(), GDScriptKind::Class)
                    } else {
                        return Err(ParsingError::new(
                            "Scripts declared as classes must contain class_name",
                            "",
                        ));
                    }
                }
                GDAnnoKind::Header(GDHeaderKind::Script) => {
                    if let Some(_) = &class_name {
                        return Err(ParsingError::new(
                            "The file declared as a script cannot have class_name",
                            "",
                        ));
                    } else {
                        (String::from("_script_"), GDScriptKind::Script)
                    }
                }
                GDAnnoKind::Header(GDHeaderKind::Singleton) => {
                    if let Some(_) = &class_name {
                        return Err(ParsingError::new(
                            "The file declared as a singleton cannot have class_name.",
                            "",
                        ));
                    } else {
                        (String::from("_singleton_"), GDScriptKind::Singleton)
                    }
                }
                _ => { // 程序逻辑错误，所以此处应触发 panic!
                    panic!("header_anno 被错误的分配了一个不是 Header 的注释")
                }
            }
        };
        Ok(GDScript {
            name,
            header: header_anno,
            elems,
            kind,
            extends,
        })
    }
}

#[cfg(test)]
mod tests {
    use std::path::Path;

    use crate::GDScript;

    #[test]
    fn test_read_from_file() {
        let f = GDScript::read_from_file(&Path::new("Entity.gd"));
        if let Err(x) = &f {
            eprintln!("{}", x);
            assert!(false)
        }
        let f = f.unwrap();
        f.write_to_file("folder").unwrap();
    }
}
